package com.ruoyi.entwechat.util;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * 企业微信工具类
 *
 * @author xueyu
 */
@Slf4j
public class EntWeChatUtil {


    private final static String ACCESS_TOKEN_CACHE_KEY = "ENT_WECHAT_ACCESSTOKEN_";
    private final static String ACCESS_JSAPI_TICKET = "ENT_WX_JSAPI_TICKET_";

    private static RedisCache getRedisCache() {
        return SpringUtils.getBean(RedisCache.class);
    }

    /**
     * 获取AccessToken
     *
     * @param corpId    企业id
     * @param appSecret 应用密钥
     * @return AccessToken 应用授权码
     */
    public static String getAccessToken(String corpId, String appSecret) {
        RedisCache redisCache = getRedisCache();
        Assert.notBlank(corpId, "corpId不能为空!");
        Assert.notBlank(appSecret, "appSecret不能为空!");
        String cacheKey = ACCESS_TOKEN_CACHE_KEY.concat(corpId);
        AccessTokenInfo accessTokenInfo;
        accessTokenInfo = redisCache.getCacheObject(cacheKey);
        if (Objects.nonNull(accessTokenInfo)) {
            log.info("从缓存中获取企微的令牌");
            return accessTokenInfo.getAccessToken();
        }
        accessTokenInfo = getAccessTokenByApi(corpId, appSecret);
        Integer expiresIn = accessTokenInfo.getExpiresIn();
        redisCache.setCacheObject(cacheKey, accessTokenInfo, expiresIn, TimeUnit.SECONDS);
        return accessTokenInfo.getAccessToken();
    }

    /**
     * 获取accessToken
     *
     * @param corpId    企业id
     * @param appSecret app密钥
     * @return AccessTokenInfo {@link AccessTokenInfo}
     * @see AccessTokenInfo
     */
    public static AccessTokenInfo getAccessTokenByApi(String corpId, String appSecret) {
        log.info("从Api中获取企微令牌!");
        String url = ApiUrl.getTokenUrl(corpId, appSecret);
        Assert.notBlank(url);
        String resultStr = HttpUtil.get(url);
        Assert.notBlank(resultStr, () -> new RuntimeException("获取企业微信授权码失败!"));
        JSONObject resultJson = JSONObject.parseObject(resultStr);
        Integer errorCode = resultJson.getInteger("errcode");
        if (errorCode != 0) {
            String errMsgStr = resultJson.getString("errmsg");
            String message = StrUtil.format("获取企微授权码失败,异常信息:{}", errMsgStr);
            log.error(message);
            throw new RuntimeException(message);
        }
        AccessTokenInfo accessTokenInfo = new AccessTokenInfo();
        String accessToken = resultJson.getString("access_token");
        Integer expiresIn = resultJson.getInteger("expires_in");
        accessTokenInfo.setAccessToken(accessToken);
        accessTokenInfo.setExpiresIn(expiresIn);
        accessTokenInfo.setCorpId(corpId);
        accessTokenInfo.setAppSecret(appSecret);
        return accessTokenInfo;
    }

    /**
     * 通过code 获取用户信息
     *
     * @param accessToken 令牌
     * @param code        code
     * @return 用户userId
     */
    public static String getUserIdByCode(String accessToken, String code) {
        return Optional.of(getAuthGetUserInfo(accessToken, code)).filter(r -> r.getInteger("errcode") == 0)
                .map(r -> r.getString("UserId")).orElse(StringUtils.EMPTY);
    }

    /**
     * 获取用户凭证票据by code
     *
     * @param accessToken accessToken
     * @param code        免登授权码
     * @return string
     */
    public static String getUserTicketByCode(String accessToken, String code) {
        return Optional.of(getAuthGetUserInfo(accessToken, code)).filter(r -> r.getInteger("errcode") == 0)
                .map(r -> r.getString("user_ticket")).orElse(StringUtils.EMPTY);
    }

    /**
     * 根据code 兑换用户基本信息
     *
     * @param accessToken app凭证
     * @param code        code
     * @return json 结果 包含企微接口返回的错误
     */
    public static JSONObject getAuthGetUserInfo(String accessToken, String code) {
        Assert.notBlank(accessToken, () -> new RuntimeException("accessToken不能为空!"));
        Assert.notBlank(code, () -> new RuntimeException("code不能为空!"));
        String url = ApiUrl.getAuthGetUserInfoUrl(accessToken, code);
        String resultStr = HttpUtil.get(url);
        Assert.notBlank(resultStr);
        log.info(StrUtil.format("{}", resultStr));
        JSONObject resultJson = JSONObject.parseObject(resultStr);
        Integer errCode = resultJson.getInteger("errcode");
        String errMsg = resultJson.getString("errmsg");
        log.info(StrUtil.format("通过code获取企业微信用户OpenId结果:{}", resultStr));
        return resultJson;
    }

    /**
     * 发送消息给企微
     *
     * @param accessToken 凭证
     * @param reqMessage  请求消息
     * @return JSON
     */
    public static JSONObject messageSend(String accessToken, String reqMessage) {
        Assert.notBlank(accessToken, () -> new RuntimeException("AccessToken不能为空!"));
        Assert.notBlank(reqMessage, () -> new RuntimeException("请求消息不能为空!"));
        String url = ApiUrl.getMessageSendUrl(accessToken);
        String result = HttpUtil.post(url, reqMessage);
        JSONObject resultJson = JSONObject.parseObject(result);
        int errCode = resultJson.getInteger("errcode");
        if (errCode != 0) {
            String errMsg = resultJson.getString("errmsg");
            log.error(StrUtil.format("调用发送企微发送消息接口失败,错误码:{},异常信息:{}", errCode, errMsg));
        }
        return resultJson;
    }

    /**
     * 获取通讯录成员信息
     *
     * @param accessToken 令牌
     * @param userId      用户id
     * @return json
     */
    public static JSONObject getAddressBookUserInfo(String accessToken, String userId) {
        Assert.notBlank(accessToken);
        Assert.notBlank(userId);
        String url = ApiUrl.getAddressBookUserInfoUrl(accessToken, userId);
        String result = HttpUtil.get(url);
        JSONObject resultJson = JSON.parseObject(result);
        int errCode = resultJson.getInteger("errcode");
        if (errCode != 0) {
            String errMsg = resultJson.getString("errmsg");
            log.error(StrUtil.format("获取通讯录成员信息失败,userId:{},错误码:{},异常信息:{}", userId, errCode, errMsg));
        }
        return resultJson;
    }

    /**
     * 免登 通过user_ticket 成员票据获取访问成员敏感信息
     *
     * @param accessToken 接口调用令牌
     * @param userTicket  成员票据
     * @return json
     */

    public static JSONObject getUserDetailByUserTicket(String accessToken, String userTicket) {
        Assert.notBlank(accessToken);
        Assert.notBlank(userTicket);
        String url = ApiUrl.getUserDetailUrl(accessToken);
        JSONObject param = new JSONObject();
        param.put("user_ticket", userTicket);
        String result = HttpUtil.post(url, param.toJSONString());
        log.info("获取成员敏感信息:{}", result);
        JSONObject resultJson = JSONObject.parseObject(result);
        int errCode = resultJson.getInteger("errcode");
        if (errCode != 0) {
            String errmsg = resultJson.getString("errmsg");
            log.error("免登-获取访问用户敏感信息失败:{}", errmsg);
        }
        return resultJson;
    }

    /**
     * 根据用户手机号获取userId
     *
     * @param accessToken accessToken
     * @param phone       手机号
     * @return string
     */
    public static String getUserIdByPhone(String accessToken, String phone) {
        Assert.notBlank(accessToken);
        Assert.notBlank(phone);
        String url = ApiUrl.getUserIdByPhone(accessToken);
        Map<String, String> reqMap = new HashMap<>(1);
        reqMap.put("mobile", phone);
        String resultS = HttpUtil.post(url, JSON.toJSONString(reqMap));
        log.info("根据手机号获取userId:{}", resultS);
        JSONObject resultJson = JSONObject.parseObject(resultS);
        Integer errcode = resultJson.getInteger("errcode");
        if (errcode.equals(0)) {
            return resultJson.getString("userid");
        } else {
            log.error("出现错误:{}", resultS);
        }
        return null;
    }

    /**
     * 获取js签名验证
     *
     * @param accessToken 令牌
     * @param corpId      企业id
     * @return obj
     */

    public static String getJsapiTicket(String accessToken, String corpId) {
        RedisCache redisCache = getRedisCache();
        Assert.notBlank(accessToken, "令牌不能为空!");
        String cacheKey = ACCESS_JSAPI_TICKET.concat(corpId);
        String jsApiTicket;
        jsApiTicket = redisCache.getCacheObject(cacheKey);
        if (StringUtils.isNotBlank(jsApiTicket)) {
            return jsApiTicket;
        }
        String urlStr = ApiUrl.getJsapiTicketUrl(accessToken);
        String result = HttpUtil.get(urlStr);
        if (StringUtils.isNotBlank(result)) {
            log.info(StrUtil.format("获取企业微信的js签名Api结果:{}", result));
            JSONObject resultJson = JSONObject.parseObject(result);
            if (resultJson.getInteger("errcode").equals(0)) {
                jsApiTicket = resultJson.getString("ticket");
                Integer expiresIn = resultJson.getInteger("expires_in");
                redisCache.setCacheObject(cacheKey, jsApiTicket, expiresIn, TimeUnit.SECONDS);
                return jsApiTicket;
            }
        }
        return null;
    }

    public static JSONObject updateTemplateCard(String accessToken, String reqMessage) {
        Assert.notBlank(accessToken, () -> new RuntimeException("AccessToken不能为空!"));
        Assert.notBlank(reqMessage, () -> new RuntimeException("请求消息不能为空!"));
        String url = ApiUrl.getUpdateTemplateCardUrl(accessToken);
        String result = HttpUtil.post(url, reqMessage);
        JSONObject resultJson = JSONObject.parseObject(result);
        int errCode = resultJson.getInteger("errcode");
        if (errCode != 0) {
            String errMsg = resultJson.getString("errmsg");
            log.error(StrUtil.format("调用更新企微模板消息接口失败,错误码:{},异常信息:{}", errCode, errMsg));
        }
        return resultJson;
    }

    @Getter
    public static class AccessTokenInfo implements Serializable {

        private static final long serialVersionUID = -1945747658974282703L;

        private String accessToken;

        private String corpId;

        private String appSecret;

        private Integer expiresIn;

        public AccessTokenInfo() {
        }

        public AccessTokenInfo(String accessToken, String corpId, String appSecret, Integer expiresIn) {
            this.accessToken = accessToken;
            this.corpId = corpId;
            this.appSecret = appSecret;
            this.expiresIn = expiresIn;
        }

        public void setAccessToken(String accessToken) {
            this.accessToken = accessToken;
        }

        public void setCorpId(String corpId) {
            this.corpId = corpId;
        }

        public void setAppSecret(String appSecret) {
            this.appSecret = appSecret;
        }

        public void setExpiresIn(Integer expiresIn) {
            this.expiresIn = expiresIn;
        }
    }

}
